Skip to content

R17: 系统性卡点修复五批(群成员管理/决策推送链/军团实时性/后端接线/UX收尾) - #9

Merged
mycyg merged 28 commits into
mainfrom
r15/wave1
Jul 16, 2026
Merged

R17: 系统性卡点修复五批(群成员管理/决策推送链/军团实时性/后端接线/UX收尾)#9
mycyg merged 28 commits into
mainfrom
r15/wave1

Conversation

@mycyg

@mycyg mycyg commented Jul 16, 2026

Copy link
Copy Markdown
Owner

依据 42 条对抗验证发现(r16-workbench-redesign/05-gap-review)与修复计划(06-gap-fix-plan,两分叉负责人代决留档)。五批全部逐单审查+独立复验,本地全量集成+web smoke 绿。要点:建群后加人/退群移出(owner 事务移交)/工作区成员移出+角色/邀请接真;escalation 进个人流+收件箱自刷+digest 三源扩源+9 死事件 deprecated;军团 run 生命周期实时+abort+后台任务接真(pulse+proactive);E3 草案双端入口/关怀开关/通知标签/OKR 标题/web 指令卡;编辑器冲突面板+多文件/日程月视图+未定期列/看板筛选/收件箱筛选+审批批量/deep-link seq 定位。

🤖 Generated with Claude Code

mycyg and others added 28 commits July 16, 2026 18:20
…ions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R17 G4 后端能力前端接线(非 E3/指令的部分):

- #22 通知类型标签: 抽 notificationTypeLabel 到 packages/ui/i18n.ts(双端共享),
  补 R15 四阶梯 DDL(work_item.due_soon/overdue/escalated_ddl/needs_owner)与
  会话消息(conversation.message/mention)的双语映射。route-components 改为 import。
- #23 web 静音清单: MUTABLE_NOTIFICATION_TYPES 同步 R15 真正经 isMutedForRecipient
  静音的类型(三档 DDL + conversation.message/mention);needs_owner 主通道是
  action_card 决策卡,刻意不列,免「勾了却照发」。
- #35 桌面静音文案: 「静音此类」按钮/toast/已静音清单改用共享标签映射,不再裸拼 type 串。
- #10 关怀开关: 通知偏好加「关怀问候/Cuu 关怀消息」开关直连 care_messages_enabled
  GET/PUT(web mute 面板 + 桌面通知视图各一处);api-client get/set 带 care 字段;
  web 文案避开 Cuu 品牌词(smoke 门)。
- #36 OKR 标题: timeline VM additive 补 objective_titles(服务端 join
  listObjectiveTitlesByIds,未命中回落 id);web/桌面时间线 OKR pill 悬停显目标名。

全仓 typecheck 绿;ui/desktop/api 相关单测绿(含新增 objective_titles 用例)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fts block

- #24 web 项目自定义指令入口: 项目主页加「自定义指令」卡。GET /api/projects/:id/instructions
  展示;能管项目→可编辑 textarea 失焦 PATCH 保存,无权(403)→只读说明。错误矩阵对齐桌面
  W4b1(403 forbidden / 422 validation / 其它 network,保存失败不回滚用户输入)。
- #9 E3 web 只读入口: 项目主页加「规划草案」小区块,拉 plan-drafts 只读展示 pending_review
  计数 + 最新草案状态;起草/审批/物化引导去桌面客户端。无权/取数失败静默降级。

不新增路由(都挂在既有 project-home)。web typecheck + routes.test 绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
建群后无法加人、无退群/移出能力的两处缺口(05-gap-review #1/#16)。按拍板 A
(不建项目成员数据层,项目成员=会话参与者的可管理化)把参与者集合从建群冻结的
快照升级为可增删对象。

- POST /api/conversations/:id/participants {user_id}:仅 collab 非 DM(main 全员
  语义/DM 2 人不变量各自 409);调用者须为参与者(collab 只对参与者可见,
  visibleConversation 天然把关);目标须同工作区活跃成员;上限沿用建群 cap(100);
  唯一约束幂等(已在群回 added=false + 现有列表)。
- DELETE /api/conversations/:id/participants/:userId:自删=退群、owner 删他人=移出;
  owner 退群且群里还有他人→最早加入者事务内自动升 owner;只剩自己→409(最后一人,
  取窄不做归档旁路)。被移出者读游标/消息保留(只删 participant 行)。
- 新增 conversation.participants.updated 事件(best-effort 广播,客户端按需重拉)。
- conversations 服务顶部记录拍板 A 决策与未来扩展点(#3)。

契约/仓库/服务/路由四层 + openapi additive;DB 仓库 12 例、服务 11 例、路由 8 例、
契约 4 例测试矩阵覆盖两不变量(DM 2 人、main 全员)与 owner 移交语义。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
workspaceMemberships.softDelete/updateRole 之前只有 QA 脚本调用、无路由暴露
(05-gap-review #15)。收进薄服务层并落权限红线后暴露:

- DELETE /api/workspace/members/:userId 移出、PATCH 改角色。
- 权限取窄:仅工作区 admin/owner(actor.isAdmin 或成员行 role∈{admin,owner});
  不能对自己动手;不能移出/降级最后一名特权成员(admin∪owner 计数≤1 时拒)。
- memberships 仓库补 listActiveByWorkspace/updateRole;成员移出/角色变更各写一笔审计。
- 角色概念确实存在,故一并补 PATCH(侦察结论:memberships.role 有 member/admin/owner)。

契约 additive(角色枚举 + 请求/结果 VM);服务 10 例、路由 6 例测试覆盖权限矩阵
(admin/owner/普通成员/非成员/自己/最后一名 admin);openapi additive。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Execute-class action-card items that couldn't be dispatched (enqueue/notify
failed after the work item existed) only marked the row `escalated` and never
wrote an ai_decisions row — so the failure never reached the decision inbox and
had no recovery entry, while decide-class items always fell an escalation.

Separate work-item creation from dispatch so a failure still has a work_item to
hang an escalation on: create a real escalation_event (trigger unqualified,
suggested lead = assignee, handoff tagged execute_dispatch_failed), push the
work item to escalated, and post a hand-off system note. The owner-less !assignee
corner stays escalated-without-work-item, matching the decide path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me stream

human-reserved escalations only reached workitem + workspace-global topics, so
a non-admin owner (who doesn't subscribe all:<workspace>) saw a new escalation
only if they happened to be parked on that work item's detail page — the desktop
workbench badge/inbox never learned of it in real time.

Also publish escalation.opened to topics.user of the escalation target — the
work item's current human owner (claimant first, else submitter), the person
who takes over pm mode. That user always has read access to the work item, so
this matches their GET /attention queue. Budget escalations already reach
topics.user via emitBudgetNotice, so they were already covered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The in-conversation "待你拍板 N 件" digest card only counted approval_requests,
so its number was systematically smaller than the decision inbox (GET /attention),
which aggregates escalations (incl. budget) + memory conflicts + approvals +
reviewable proposals.

Union the three project-attributable sources (pending approvals + unresolved
escalations + opened/reviewed proposals, each via work_item -> project) so the
digest count matches the inbox. Scope stays narrow: memory conflicts are per-user
(no project) and personal/cross-project approvals without a work_item can't be
attributed to a project, so neither is stuffed into a project card. The digest is
a project-level count (all members, one card), so it is not per-actor filtered —
documented in the repository. Adds unionAll passthrough to the test query recorder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
把 E3 项目规划草案(起草/列表/详情/批准/驳回/物化五端点)接成桌面日程左栏可用:

- 「用 Cuu 起草计划」按钮 → 起草表单(规划意图 textarea)→ POST /plan-drafts;
  LLM 未配置服务端回 503(人话 message 直接透传到错误横幅)。
- 草案列表:列全部草案(含 pending_review),状态 chip(待审阅/已批准/已驳回/已物化);
  点开进详情面板(里程碑/工作项/理由/审阅意见)。
- 详情动作接现成端点:pending_review→批准/驳回(带理由);approved→物化到时间线;
  materialized→结果计数。忙态全禁用,物化成功后重拉时间线 VM,新里程碑/工作项立即落到周历。
- 无管理权(草案 GET 403)→ 左栏退回里程碑回落、不给起草假入口(04 §4 铁律 3)。
  无需改 shell(自判权限),mountScheduleView 签名不变。

新增 schedule/api.ts 五个薄封装 + render 计划面板 + view 状态机 + css;render.test 19 用例;
桌面全量 1330 测试绿 + 全仓 typecheck 绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mountAttentionInbox now returns { dispose, refresh } instead of a bare dispose
function. Spotlight is unchanged — createAttentionView unwraps to .dispose, so
the focus box behaves verbatim. The workbench inbox view forwards refresh() so
its host (shell) can re-pull the list when the badge refreshes, letting an open
decision inbox update live instead of needing a tab switch away and back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + tab UX

#6: the /me stream handler hard-filtered on `event.type !== "notification.created"`,
dropping escalation/budget/sync_conflict (and permission/proposal) events that
really reach topics.user and feed GET /attention — the badge only caught up via
30s polling, while the same /me topic drove cards fine on the pet channel
(desktop-cuu-runtime). Replace with a decision-class whitelist so any of those
refreshes the badge, aligning the two consumers.

#17: refreshInboxBadge also calls inboxHandle.refresh() when centerTab==='inbox',
so an open decision inbox re-pulls its list alongside the badge.

#37: middle-click (auxclick button 1) on a session tab closes it.
#38: Cmd/Ctrl+W closes the current session tab; Cmd/Ctrl+1..9 switch to the Nth
open tab. Narrowed: only intercepts (preventDefault) when the center tab is a
conversation and a matching tab exists — otherwise browser/system semantics pass
through. The doc-level keydown listener is removed on dispose/logout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
E3 草案双端入口(桌面日程全生命周期:起草/审/物化,403 无假 affordance;web 项目主页只读卡)/关怀开关双端/通知标签共享函数(packages/ui i18n,四 DDL 类型+会话消息双语)/web 静音清单同步(needs_owner 刻意排除防欺骗)/桌面静音文案标签化/OKR 标题 join(additive)/web 指令卡。负责人已审查+复验绿(desktop 1330/ui 209/web 92/api;G4 自跑 web smoke 82 步 ok)。
…r-run subs

Verified every SSE event type against its production publishers. 9 are never
published outside test fixtures: confidence.scored, knowledge.evidence.ready,
sync.progress, usage.recorded, conversation.tool.{begin,output_delta,end},
conversation.item.{started,completed}. Marked each @deprecated in enums.ts —
values are KEPT (eventTypeSchema is built from Object.values; deleting breaks
contracts compat), the notes just warn against picking them for new producers.

#31: documented the clarification / knowledge_result AttentionItem kinds as
producer-less dead branches — enum values and the double-sided UI switch arms
stay (exhaustive rendering), per instruction.

#25 per-run subs: removed budget.*/proposal.* from desktopCuuRunStreamEventNames.
They publish only to workitem/user topics, never topics.run, so on the per-run
SSE stream they never fired — dead subscriptions. Budget/proposal cards still
reach the desktop via the /me channel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
桌面 UI 把已就绪的后端能力接真、把无法兑现的承诺改成真话(05-gap-review #14/#15/#2)。

- #14 邀请成员:admin/owner 在成员 roster 头部出「邀请」入口——填邮箱 → POST
  /api/auth/invites 生成一次性 token → 复制到剪贴板 + 提示。无未过期邀请列表读端点,
  故只做生成 + 复制(报告注明)。
- #15 成员移出:roster「管理」模式每行出「移出」(内联确认 + 忙态)→ DELETE
  /api/workspace/members/:userId → 乐观就地从花名册剔除。管理/邀请入口仅 admin/owner
  (viewer.membership_role !== 'member')才渲。
- #2 项目设置「成员」分区:主区=全员(工作区总数)+ 各协同会话概览(会话名 + 「管理成员」
  跳到该会话成员条);把新建项目弹窗「成员邀请之后在项目设置里调」改成与现实一致的文案
  (成员在各协同会话里加人/退群)。
- chat/api.ts 补 post/deleteConversationParticipant 客户端薄封装(供会话成员条用)。

rail 5 例 + settings 2 例渲染测试覆盖 canManage 门控、管理模式、内联确认、邀请框、成员概览。
CSS 样式与会话成员条(#1/#16)合并在后续一笔补齐。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
execute 派发失败落真实 escalation(进收件箱)/escalation.opened 增发 topics.user(认领人优先)/me-stream 硬过滤改决策类白名单(与桌宠通道对齐,含 permission.*/proposal.opened)/收件箱 refresh() 自刷(spotlight 零变化)/digest 三源扩源(项目级口径注释在案)/9 个死事件 @deprecated+删 4 条 per-run 死订阅(枚举保留)/tab 中键关闭+Cmd+W+Cmd+1..9。负责人代决两拍板:digest 不做 per-actor 收窄、白名单保持宽集。已审查+复验绿(desktop 1324/api/db)。
…ion topic (#7)

emitRunEvent now also publishes the status-level lifecycle subset
(started/failed/escalated + the succeeded terminal agent_run.step[kind:'done'])
to topics.conversation(source_conversation_id) when a run carries one. The army
context panel subscribes to that conversation stream, so a purely-executing run
no longer stalls on the snapshot captured when the conversation was opened.
Per-step high-frequency increments are NOT dual-published (same trade-off as the
workitem stream) to avoid fan-out amplification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surfaces the previously-invisible background machinery on the army panel:
 - scheduler: pulse scheduler per-task heartbeat (name / interval / last tick /
   tick+skip+error counts). Process-level, no per-workspace data, and error text
   (last_error_message) is deliberately dropped from the VM so non-admin members
   don't see internal error detail. enabled=false when PULSE_SCHEDULER_ENABLED is off
   (never instantiates the scheduler).
 - proactive: the current user's most recent proactive intents
   (listRecentProactiveIntentsForUser, scoped to workspace + target_user), delivered
   and suppressed alike, capped at 10.

Auth = workspace member (requireCurrentUser + humanScope). Contracts/openapi additive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
agentRunStatuses only has queued/running/succeeded/failed/escalated/cancelled;
budget_exhausted is a budget-notice reason, never a run status, so this label
matched nothing. Dead entry removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lldown, background (#7/#8/#19/#20/#21/#32/#33)

Desktop army panel wiring for the G3 gaps:
 - #7  panel refreshes on run lifecycle events forwarded through the conversation
       stream (isIncomingAgentRunLifecycleEvent; new army/events.ts).
 - #8  background-tasks section re-rendered from GET /api/army/background (scheduled
       tasks + proactivity feed), honest loading/error/empty states; lazy-loaded once.
 - #19 abort entry on cancellable run cards + a confirm→busy→status flow in the run
       detail, wired to the existing abortAgentRun (/agent-runs/:id/abort).
 - #20 escalated split out of the wait bucket into its own danger "waiting on you"
       badge + a "handle" action that opens the decision inbox.
 - #21 army-overview cards drill down: selectProject + auto-open the run detail in
       the right pane (openRunId threaded through showForConversation).
 - #32 overview header "loaded N minutes ago" annotation next to the refresh button.
 - #33 returning from run detail to the list re-fetches the conversation panel.

Interactive run cards are restructured to div + inner hit-button + footer actions to
keep action buttons out of nested buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
run 生命周期双发源会话 topic(仅状态级子集,不放大扇出)/GET /api/army/background(pulse 心跳进程级+proactive 按人收窄,不泄内部错误)/abort 入口/escalated 危色徽标+去处理→收件箱/总览下钻/过期提示/返回重拉/删死标签。负责人已审查+复验绿(agent-runs 98/army 71/app 41)。
 桌面端)

把新端点接进会话头部成员条——仅非 DM 的 collab 会话渲「成员」「加人」两个入口
(main/DM 不变):

- 加人:管理面板内联候选清单(工作区成员 - 既有参与者 - Cuu 哨兵),逐个添加,
  忙态「添加中…」+ 全候选禁用防并发;成功用返回的权威列表替换本地参与者 +
  已读 N/M 分母(otherMemberIds)。
- 退群/移出:参与者名单每行按权限渲控件——自己那行恒「退出」;别人那行仅群主
  (participant_role=owner)渲「移出」。行内确认(无 window.confirm)+ 忙态。
  自己退群成功 → 关流 + 「你已退出」终态(不再渲头部/滚动区/输入框)。
- SSE:消费 conversation.participants.updated(镜像 cuu.updated 接线)——别端加/退/
  移后按需重拉 GET /participants;本人被移出直接切终态(不再打注定 404 的重拉)。
- 错误映射 memberManageErrorText:cap/权限/最后一人等已知 code 双语文案,未知 code
  落通用重试语,不泄内部错误码。

render.test.ts 新增 11 例纯函数测试(候选排除/权限矩阵/行内确认/忙态/空态/错误映射/
终态);chat 全套 510 例、desktop-webview 全套 1341 例、全仓 typecheck 绿。

已知缝(诚实记录):成员条头像行与「N 位成员」计数仍渲工作区全员(保持既有行为与
测试不动),子集群的权威名单以管理面板为准。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
加人端点(main/DM 双 409 不变量+幂等+cap)/退群移出(owner 事务移交最早 member,最后一人 409)/工作区成员移出+角色 PATCH(last-admin 保护+审计)/邀请接真(一次性 token 复制,列表待读端点)/成员条管理面板+SSE 同步/项目设置成员分区+兑现弹窗文案/拍板A注释。负责人已审查+复验绿(api 1716/db 424/desktop 1341/contracts 168,~69 新测)。

# Conflicts:
#	packages/contracts/src/enums.ts
#12 变更编辑器合并撞真实冲突(WorkHubApiError code=merge_conflict)时,分支
识别并复用 Spotlight 既有的逐冲突解决面板(proposalMergeConflictHtml +
classifyProposalConflictActionHref + web-runtime 载荷解析 + applyMergeProposalCandidate/
mergeProposal),不再一律渲「状态变了刷新看看」;其它 409 保持原文案。冲突态
新增 conflict 视图模式,面板动作/返回把手就地接管,成功后 onSettled + reload 回 diff。

#13 同一提议含多个文本变动文件时,编辑器顶部加文件切换条(文件名 chips + diffstat
+ 上一个/下一个按钮)。文件集合取自 manifest.changes 里可逐行对照的文本变更(口径同
openProposalInEditor);diff 逐访问缓存 + 首屏后台预取(文件数由 manifest 有界,非无
上限翻页),加载过的文件显真实 +新增/−删除,未加载的退回 change_type 短标签;动作条
是提议级、切文件不变。

editor scoped CSS:切换条 + 冲突卡族(工作台窗口不注入 proposalCss,就地补齐)。
render.test.ts +2(多文件切换条 / 冲突面板)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
看板顶部加负责人下拉(取自卡片集合 assignee 去重,含「未指派」)+ 关键词输入
(标题/编号大小写不敏感匹配)+ 清除按钮。纯前端过滤不重新取数,四列计数随过滤更新,
过滤后总数显「命中 / 总数(已筛选)」;过滤到零给「无匹配 / 清除筛选」提示而非四个空列。
关键词整窗重渲后还原输入框焦点与光标位置。render.test.ts +4(去重/过滤纯函数 + 计数 + 无匹配)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#26 无 due_at 的工作项此前在日程里被 if(!due) continue 静默丢弃(看板/时间线都诚实标
「未定期」)。周历/月历底部加「未定期」小列(计数 + 列表),扫描口径与看板一致;点击复用
日程卡 data-wb-sc-card 通道跳时间线该行。全部有 due_at 时不渲。

#28 日程加「周/月」切换 chip。月视图=当月 6×7 网格(周一起,溢出日淡显、今天高亮),
任务压缩成状态着色小点 + 超 4 折「+K」,里程碑标 pin;点某天切回周视图并定位到那周
(weekOffsetForDay 算整周偏移)。上一/下一按当前模式移动一周或一月;「今天」归零两个偏移。

两项同属日程 render 重构,合并提交。render.test.ts +7(月网格/切换/computeScheduleMonth/
weekOffsetForDay/未定期小列),并把旧「undated 静默丢弃」断言更新为「落未定期小列」。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
收件箱顶部加按 kind 筛选 chips(全部/审批/提议/升级/预算/冲突,带计数,只渲队列里真实
出现的组)+ 批量「全部通过」(仅 approval 类,带二次确认,逐条调用现有 respondApproval
端点、失败逐条标注到对应卡)。其它 kind 语义不适合批量(升级选处理方式/提议看 diff/预算看
额度/冲突逐条挑方案),不做。

筛选/批量态只活在工作台壳层(activeFilter/batchState/failedItemIds),不进共享
mountAttentionInbox 内部状态——共享 mount 照旧只渲队列 + 动作,spotlight 聚焦盒零改。
壳层用 MutationObserver 观察共享 mount 每次重渲后的 body:重算各组计数、按筛选隐藏不匹配
的卡、把批量失败标注回贴到对应卡(refresh 后仍在);disconnect/observe 包住自身 DOM 改动
避免自激。attention.ts 仅加 benign data-att-kind 元数据供 DOM 过滤。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
搜索命中会话消息的 deep_link 已带 seq(后端契约既有)。前端全链路透传:
- PendingWorkbenchDeepLinkTarget 加可选 seq(只在有 conversationId 时有意义,stash/consume
  round-trip,非法/无会话时退回会话级不带定位);
- 搜索会话行加 data-search-seq,SearchOpenAction/resolveSearchRowAction 解析并透传,
  openInWorkbench 冷启动 stash 带 seq;
- boot 消费后 selectProject(projectId, conversationId, seq);shell 存 pendingChatFocus,
  打开对应会话的 chat 视图(主区/协同/DM 三处)时消费一次,传 focusSeq 给 mountChatView;
- chat 视图首屏就绪后 jumpToMessageBySeq(seq):复用引用/置顶跳转的 jumpToMessage(本地无就
  beforeSeq 翻页加载到为止 + 硬熔断),解析成 message id 后滚动到该消息并短暂高亮;翻不到就
  诚实不定位(会话已在最新屏)。

shell 只动 deep-link/selectProject/chat-mount 接线,不碰 me-stream/快捷键区。
测试:pending-deep-link +3(seq round-trip / 无会话丢 seq / 非法 seq 兜底)、
search +seq 解析 3 例 + 行 data-search-seq + stash seq 断言。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
编辑器 merge_conflict 复用 Spotlight 冲突面板+多文件切换条(diff 缓存+有界预取)/日程未定期列+月视图(点日切周定位)/看板负责人+关键词纯前端筛选/收件箱 kind 筛选+审批批量(壳层 MutationObserver 叠加,spotlight 零改)/deep-link seq 全链透传+seq 定位高亮。负责人已审查+复验绿(desktop 1346)。
@mycyg
mycyg merged commit 5ce23b1 into main Jul 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant